import logging

from sqlalchemy import and_, select
from sqlalchemy.exc import DBAPIError, IntegrityError

from dc_shop.core.exceptions.common import RepoError
from dc_shop.core.exceptions.user import UserIdAlreadyExist, UserIdNotExist, UserTgIdAlreadyExist, UserTgIdNotExist
from dc_shop.core.models.dto import UserDTO
from dc_shop.core.models.enums.user import UserRole
from dc_shop.database.converters.user import convert_db_model_to_user_dto
from dc_shop.database.exception_mapper import exception_mapper
from dc_shop.database.models import User
from dc_shop.database.repositories.base import SQLAlchemyRepo

logger = logging.getLogger(__name__)


class UserRepoImpl(SQLAlchemyRepo):
    @exception_mapper
    async def get_user_by_id(self, user_id: int, as_dto: bool = True) -> User | UserDTO:
        """Получаем пользователя по id"""

        user: User | None = await self._session.scalar(
            select(User)
            .where(
                User.id == user_id,
            )
            .with_for_update()
        )
        if not user:
            raise UserIdNotExist

        if not as_dto:
            return user

        user_dto = convert_db_model_to_user_dto(user)

        return user_dto

    @exception_mapper
    async def get_user_by_tg_id(self, user_tg_id: int, as_dto: bool = True) -> User | UserDTO:
        """Получаем пользователя по telegram id"""

        user: User | None = await self._session.scalar(
            select(User).where(
                User.telegram_id == user_tg_id,
            )
        )
        if not user:
            raise UserTgIdNotExist

        if not as_dto:
            return user

        user_dto = convert_db_model_to_user_dto(user)

        return user_dto

    @exception_mapper
    async def create_user(self, user: User) -> None:
        """Создаем пользователя"""

        self._session.add(user)
        try:
            await self._session.flush((user,))
        except IntegrityError as err:
            self._parse_error(err)

    @exception_mapper
    async def update_user(self, user: User) -> None:
        """Обновляем пользователя"""

        try:
            await self._session.merge(user)
        except IntegrityError as err:
            self._parse_error(err)

    @exception_mapper
    async def get_users(self) -> list[UserDTO]:
        """Получаем пользователей"""

        res = await self._session.scalars(select(User))
        users: list[User] = list(res)

        users_dtos = [convert_db_model_to_user_dto(user) for user in users]

        return users_dtos

    @exception_mapper
    async def get_users_by_role_and_shop_id(self, role: UserRole, shop_id: int) -> list[UserDTO]:
        """Получаем пользователей с ролью в магазине"""

        res = await self._session.scalars(select(User).where(and_(User.role == role, User.shop_id == shop_id)))
        users: list[User] = list(res)

        users_dtos = [convert_db_model_to_user_dto(user) for user in users]

        return users_dtos

    @staticmethod
    def _parse_error(err: DBAPIError) -> None:
        match err.__cause__.__cause__.constraint_name:  # type: ignore
            case "pk_user":
                raise UserIdAlreadyExist from err
            case "uq_user_telegram_id":
                raise UserTgIdAlreadyExist from err
            case _:
                raise RepoError from err
